package com.xiaoshuidi.cloud.module.iot.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xiaoshuidi.cloud.module.iot.dal.model.device.TbDeviceDosageCallbackModel;
import com.xiaoshuidi.cloud.module.iot.dal.pojo.TbDeviceDo;
import com.xiaoshuidi.cloud.module.iot.dal.pojo.TbDoorlockPassword;
import com.xiaoshuidi.cloud.module.iot.dal.repository.DeviceRepository;
import com.xiaoshuidi.cloud.module.iot.dal.repository.TbDoorLockPasswordRepository;
import com.xiaoshuidi.cloud.module.iot.enums.*;
import com.xiaoshuidi.cloud.module.iot.service.CacheService;
import com.xiaoshuidi.cloud.module.iot.service.KunShanCallbackService;
import com.xiaoshuidi.cloud.module.iot.service.LocalCallbackService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.Objects;

@Slf4j
@Service
public class KunShanCallbackServiceImpl implements KunShanCallbackService {

    private static final String MSG_TYPE_ADD_FINGERPRINT = "59";


    @Autowired
    private DeviceRepository deviceDataAccess;

    @Autowired
    private TbDoorLockPasswordRepository doorLockPasswordDataAccess;
    @Autowired
    private CacheService cacheService;
    @Autowired
    private LocalCallbackService localCallbackService;


    @Override
    public void statusCallback(JSONObject object) {
        String msgType = object.getString(KunShanPayloadFiledConstants.MSG_TYPE);
        if (msgType.equals(KunShanMsgTypeEnums.status.getCode())) {
            dealStatusCallBack(object);
            return;
        }
        if (msgType.equals(KunShanMsgTypeEnums.on_off_line.getCode())) {
            dealOnOffLineCallBack(object);
            return;
        }
        if (msgType.equals(KunShanMsgTypeEnums.batch_on_off_line.getCode())) {
            dealBatchOnOffLineCallBack(object);
            return;
        }
        log.debug("其余类型的状态回调暂不做处理");
    }

    private void dealBatchOnOffLineCallBack(JSONObject originalCallBackMsg) {
        JSONObject dataJsonObject = originalCallBackMsg.getJSONObject(KunShanPayloadFiledConstants.RESP_DATA);
        if (null == dataJsonObject) {
            log.error("琨山回调内容为空");
            return;
        }
        JSONArray lockIdJsonArray = originalCallBackMsg.getJSONArray(KunShanPayloadFiledConstants.LOCK_ID);
        if (null == lockIdJsonArray) {
            log.error("琨山回调批量在线离线状态时未传递设备信息");
            return;
        }
        Integer originalOnline = dataJsonObject.getInteger(KunShanPayloadFiledConstants.ON_LINE);
        Object[] lockIdObjectArray = lockIdJsonArray.stream().toArray();
        for (Object lockIdObject : lockIdObjectArray) {
            String lockId = (String) lockIdObject;
            dbUpdateForOnOffLine(lockId, originalOnline);
        }
    }

    private void dealOnOffLineCallBack(JSONObject originalCallBackMsg) {
        JSONObject dataJsonObject = originalCallBackMsg.getJSONObject(KunShanPayloadFiledConstants.RESP_DATA);
        if (null == dataJsonObject) {
            log.error("琨山回调内容为空");
            return;
        }
        String thirdDeviceNo = originalCallBackMsg.getString(KunShanPayloadFiledConstants.LOCK_ID);
        // 1-连线 2-离线
        Integer onlineStatus = dataJsonObject.getInteger(KunShanPayloadFiledConstants.ON_LINE);
        dbUpdateForOnOffLine(thirdDeviceNo, onlineStatus);
    }

    private void dbUpdateForOnOffLine(String thirdDeviceNo, Integer onlineStatus) {
        TbDeviceDo tbDeviceDo = new TbDeviceDo();
        tbDeviceDo.setDeviceName(thirdDeviceNo);
        tbDeviceDo.setStatus(1);
        // 1-在线 0-离线,琨山的离线在线更改为本地记录表的在线离线
        int localOnlineStatus = onlineStatus == 1 ? onlineStatus : 0;
        tbDeviceDo.setOnlineStatus(localOnlineStatus);
        tbDeviceDo.setUpdateTime(LocalDateTime.now());
        try {
            int effectiveCount = deviceDataAccess.updateByIndex(tbDeviceDo);
            if (effectiveCount != 1) {
                log.warn("琨山状态回调时,更新设备={} 的离线状态失败", thirdDeviceNo);
            }
        } catch (Exception e) {
            log.error("琨山回调更新设备={} 离线在线状态时发生错误", thirdDeviceNo, e);
        }
    }

    /**
     * 对于我们系统而言仅关注状态回调的电量
     *
     * @param originalCallBackMsg 回调原始消息
     */
    private void dealStatusCallBack(JSONObject originalCallBackMsg) {
        JSONObject dataJsonObject = originalCallBackMsg.getJSONObject(KunShanPayloadFiledConstants.RESP_DATA);
        if (null == dataJsonObject) {
            log.error("琨山回调内容为空");
            return;
        }
        String batteryPercentToken = dataJsonObject.getString(KunShanPayloadFiledConstants.BATTERY_PERCENT);
        String thirdDeviceNo = originalCallBackMsg.getString(KunShanPayloadFiledConstants.LOCK_ID);
        Long seconds = dataJsonObject.getLong(KunShanPayloadFiledConstants.LAST_REFRESH);
        Date lastRefreshDate = new Date(seconds * 1000);
        TbDeviceDo tbDeviceDo = new TbDeviceDo();
        tbDeviceDo.setDeviceName(thirdDeviceNo);
        tbDeviceDo.setStatus(1);
        tbDeviceDo.setBatteryLevel(Double.parseDouble(batteryPercentToken));
        tbDeviceDo.setUpdateTime(LocalDateTime.now());
//        tbDeviceDo.setReportTime(lastRefreshDate);
        tbDeviceDo.setReportTime(LocalDateTime.now());
        try {
            int effectiveCount = deviceDataAccess.updateByIndex(tbDeviceDo);
            if (effectiveCount != 1) {
                log.warn("琨山状态回调时,更新设备={} 的电量失败", thirdDeviceNo);
            }
        } catch (Exception e) {
            log.error("琨山回调更新设备={} 电量时发生错误", thirdDeviceNo, e);
        }
    }

    @Override
    public void operate(JSONObject object) {
        log.info("===========琨山门锁operate回调:{}", object.toJSONString());
        // 53 新增密码 54 删除密码 55 修改密码   67 解冻 冻结
        String msgType = object.getString("msgType");
        TbDeviceDo tbDeviceDo = new TbDeviceDo();
        tbDeviceDo.setDeviceThirdNo(object.getString("lockId"));
        tbDeviceDo.setBrandNo(BrandEnum.KUNSHAN.getBrand());
        tbDeviceDo.setStatus(1);
        tbDeviceDo = deviceDataAccess.getTbDeviceByConditon(tbDeviceDo);
        if (tbDeviceDo != null) {
            JSONArray dataArray = object.getJSONArray(KunShanPayloadFiledConstants.RESP_DATA);
            if (null == dataArray || dataArray.size() == 0) {
                log.error("琨山关于密码的回调内容为空");
                return;
            }
            TbDeviceDo finalTbDeviceDo = tbDeviceDo;
            dataArray.stream().forEach((data) -> passwordOperateHelp((JSONObject) data, finalTbDeviceDo, msgType));
        }
    }

    private void passwordOperateHelp(JSONObject data, TbDeviceDo tbDeviceDo, String msgType) {
        String result = data.getString("result");
        String index = data.getString("index");
        TbDoorlockPassword tbDoorlockPassword = new TbDoorlockPassword();
        tbDoorlockPassword.setDeviceNo(tbDeviceDo.getDeviceNo());
        tbDoorlockPassword.setAuthType(Integer.parseInt(PasswordTypeEnum.PASSWORD.getTypeCode()));
        tbDoorlockPassword.setStatus(1);
        switch (msgType) {
            case "53":
                // 上传在线密码时传递的 id 字段,同时也是db中存在的字段;当新增密码回调到达后,将db中的第三方密码id更正为返回的index
                String requestId = data.getString("requestId");
                tbDoorlockPassword.setThirdPasswordId(requestId);
                addPassword(tbDoorlockPassword, result, index);
                break;
            case "54":
                tbDoorlockPassword.setThirdPasswordId(index);
                deletePassword(tbDoorlockPassword, result);
                break;
            case "55":
                tbDoorlockPassword.setThirdPasswordId(index);
                updatePassword(tbDoorlockPassword, result);
                break;
            case "67":
                tbDoorlockPassword.setThirdPasswordId(index);
                unfrozenPassword(tbDoorlockPassword, result);
                break;
        }
    }

    private void unfrozenPassword(TbDoorlockPassword tbDoorlockPassword, String result) {
        //冻结操作结果 0 解冻状态 1 冻结状态 2 密码不存在（琨山接口文档中说的-1是错的，咨询后确定是返回2）
        if (result.equals("0")) {
            tbDoorlockPassword.setPasswordStatus(1);
            doorLockPasswordDataAccess.updateDoorLockPasswordRecordByCondition(tbDoorlockPassword);
        } else if (result.equals("1")) {
            tbDoorlockPassword.setPasswordStatus(0);
            doorLockPasswordDataAccess.updateDoorLockPasswordRecordByCondition(tbDoorlockPassword);
        } else {
            log.error("传递第三方密码index有误,第三方未查询到对应的index={}", tbDoorlockPassword.getThirdPasswordId());
        }
    }


    private void updatePassword(TbDoorlockPassword tbDoorlockPassword, String result) {
        TbDoorlockPassword password = doorLockPasswordDataAccess.selectTbDoorlockPassword(tbDoorlockPassword);
        password.setPasswordStatus(1);
        doorLockPasswordDataAccess.updateByPrimaryKeySelective(password);
    }

    /**
     * 删除密码是status字段的更新,在 {@code  spring.cloud.sdiot.doorlock.service.impl.DoorLockServiceImpl#deletePassword(Integer, AddPasswordModel)}
     * 已经做过状态删除，为了保持代码一致性，这里当回调状态失败时才回滚到之前的状态
     *
     * @param tbDoorlockPassword 门锁密码请求
     * @param result             回调状态 0-成功 1-失败
     */
    private void deletePassword(TbDoorlockPassword tbDoorlockPassword, String result) {
        //因回调时，还未做任何db修改， 此处密码自身状态依然是生效状态1，回调时将其改为过期状态2
        tbDoorlockPassword.setPasswordStatus(PasswordStatusEnum.VALID.getCode());
        TbDoorlockPassword orignialTbDoorlockPassword = doorLockPasswordDataAccess.selectTbDoorlockPassword(tbDoorlockPassword);
        //只对过期密码作处理，接收到回调消息无法确认是正常删除请求的回调 还是 密码过期琨山删除Api的请求, 利用时间来判断是否为密码过期的请求, 过期请求兼容正常删除请求
        if(Objects.nonNull(orignialTbDoorlockPassword) && orignialTbDoorlockPassword.getValidEndtime().getTime() < System.currentTimeMillis() && result.equals("0")){
            orignialTbDoorlockPassword.setStatus(0);
            orignialTbDoorlockPassword.setPasswordStatus(PasswordStatusEnum.EXPIRED.getCode());
            orignialTbDoorlockPassword.setUpdateTime(LocalDateTime.now());
            doorLockPasswordDataAccess.updateByPrimaryKeySelective(orignialTbDoorlockPassword);
        } else {
            tbDoorlockPassword.setStatus(0);
            TbDoorlockPassword password = doorLockPasswordDataAccess.selectTbDoorlockPassword(tbDoorlockPassword);
            if (Objects.isNull(password)) {
                log.error("删除密码回调时未找到对应下标的已失效密码");
                return;
            }
            if (result.equals("1")) {
                tbDoorlockPassword.setStatus(PasswordStatusEnum.VALID.getCode());
                doorLockPasswordDataAccess.updateByPrimaryKeySelective(password);
            }
        }
    }

    /**
     * 指纹与在线密码的第三方id,都是先存入到数据库的为请求id,当回调响应后才正式开始。
     * @param tbDoorlockPassword 数据查询参数
     * @param result 回调结果
     * @param index 经第三方确认的id
     */
    public void addPassword(TbDoorlockPassword tbDoorlockPassword, String result, String index) {
        TbDoorlockPassword password = doorLockPasswordDataAccess.selectTbDoorlockPassword(tbDoorlockPassword);
        if (password == null) {
            log.error("设备{} 下标为 {} 状态为 {} 的密码记录不存在", tbDoorlockPassword.getDeviceNo(), tbDoorlockPassword.getThirdPasswordId(), tbDoorlockPassword.getPasswordStatus());
            return;
        }
        if (result.equals("0")) {
            password.setPasswordStatus(1);
            password.setThirdPasswordId(index);
        } else {
            password.setPasswordStatus(5);
        }
        doorLockPasswordDataAccess.updateByPrimaryKeySelective(password);
    }

    @Override
    public void deviceMessage(JSONObject object) throws Exception {
        String lockId = object.getString("lockId");
        JSONObject jsonObject = object.getJSONObject("data").getJSONArray("openDoorRecord").getJSONObject(0);
        String passIndex = jsonObject.getString("passIndex");
        // 琨山门锁区别密码开门方式，且昆山门锁没有离线开门记录
        String unlockWay = jsonObject.getString("unlockway");
        String opType = mapOpenDoorWay(unlockWay);
        if (StringUtils.isEmpty(opType)) {
            return;
        }
        //先查询该设备是否存在
        TbDeviceDo tbDeviceDo = new TbDeviceDo();
        tbDeviceDo.setDeviceThirdNo(lockId);
        tbDeviceDo.setBrandNo(BrandEnum.KUNSHAN.getBrand());
        tbDeviceDo.setStatus(1);
        tbDeviceDo = deviceDataAccess.getTbDeviceByConditon(tbDeviceDo);
        if (tbDeviceDo != null) {
            JSONObject openRecord = new JSONObject();
            openRecord.put("userId", passIndex);
            openRecord.put("recordTime", new Date());
            //开门方式
            openRecord.put("opType", opType);
            TbDeviceDosageCallbackModel tbDeviceDosageCallbackModel = new TbDeviceDosageCallbackModel();
            tbDeviceDosageCallbackModel.setOpenRecord(openRecord);
            tbDeviceDosageCallbackModel.setTbDeviceDo(tbDeviceDo);
            localCallbackService.disposeOpenRecordCallback(tbDeviceDosageCallbackModel);
        }
    }

    @Override
    public void fingerOperate(JSONObject object) {
        String msgType = object.getString("msgType");
        TbDeviceDo tbDeviceDo = new TbDeviceDo();
        tbDeviceDo.setDeviceThirdNo(object.getString("lockId"));
        tbDeviceDo.setBrandNo(BrandEnum.KUNSHAN.getBrand());
        tbDeviceDo.setStatus(1);
        tbDeviceDo = deviceDataAccess.getTbDeviceByConditon(tbDeviceDo);
        if (tbDeviceDo != null) {

            if (MSG_TYPE_ADD_FINGERPRINT.equals(msgType)) {
                JSONObject data = object.getJSONObject("data");
                fingerOperateHelper(data, msgType, tbDeviceDo);
            } else {
                JSONArray dataJsonArray = object.getJSONArray("data");
                if (null == dataJsonArray || dataJsonArray.size() == 0) {
                    log.error("指纹操作回调没有提供操作数据");
                    return;
                }
                TbDeviceDo finalTbDeviceDo = tbDeviceDo;
                dataJsonArray.stream().forEach((data) -> fingerOperateHelper((JSONObject) data, msgType, finalTbDeviceDo));
            }

        }
    }

    private void fingerOperateHelper(JSONObject data, String msgType, TbDeviceDo tbDeviceDo) {
        String result = data.getString("result");
        String index = data.getString("index");
        TbDoorlockPassword tbDoorlockPassword = new TbDoorlockPassword();
        tbDoorlockPassword.setDeviceNo(tbDeviceDo.getDeviceNo());
        tbDoorlockPassword.setAuthType(Integer.parseInt(PasswordTypeEnum.FINGER_DIGIT.getTypeCode()));
        tbDoorlockPassword.setStatus(1);
        switch (msgType) {
            case "59":
                //该requestId为上传指纹时传递的requestId,并存入到数据库中;当回调返回时再用index更正requestId到第三方密码编号那里
                String requestId = data.getString("requestId");
                tbDoorlockPassword.setThirdPasswordId(requestId);
                addPassword(tbDoorlockPassword, result, index);
                break;
            case "57":
                tbDoorlockPassword.setThirdPasswordId(index);
                deletePassword(tbDoorlockPassword, result);
                break;
            case "66":
                tbDoorlockPassword.setThirdPasswordId(index);
                unfrozenPassword(tbDoorlockPassword, result);
                break;
            case "61":
                tbDoorlockPassword.setThirdPasswordId(index);
                tbDoorlockPassword.setAuthType(Integer.parseInt(PasswordTypeEnum.FINGER_DIGIT.getTypeCode()));
                updatePassword(tbDoorlockPassword, result);
                break;
        }
    }

    /**
     * 将琨山开门的方式与我们系统中定义的开门方式进行映射
     *
     * @param unLockWay 消息类型50的回调通知中的开门方式
     * @return 系统中对应的开门方式, null 表示错误
     */
    private String mapOpenDoorWay(String unLockWay) {
        KunShanOpenDoorWayEnum instance = KunShanOpenDoorWayEnum.getInstance(unLockWay);
        if (null == instance) {
            log.error("琨山门锁开门记录回调时未指定开门方式或开门方式超出定义范围");
            return null;
        }
        return instance.getMapping();
    }
}
